package org.example.ding.api;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api")
public class RecommendationController {

    public static final DishCatalog DISH_CATALOG = new DishCatalog();
    public static final UserProfileService USER_SERVICE = new UserProfileService();
    public static final RecommendationService RECOMMENDATION_SERVICE = new RecommendationService(DISH_CATALOG, USER_SERVICE);

    // --- Endpoints ---

    @GetMapping("/dishes")
    public List<Dish> listDishes() {
        return DISH_CATALOG.getAll();
    }

    @GetMapping("/users/{userId}/profile")
    public UserProfile getProfile(@PathVariable String userId) {
        return USER_SERVICE.getOrCreate(userId);
    }

    @GetMapping("/users/{userId}/recommendations")
    public List<DishScore> recommend(
            @PathVariable String userId,
            @RequestParam(name = "mealType", required = false, defaultValue = "LUNCH") MealType mealType,
            @RequestParam(name = "limit", required = false, defaultValue = "10") int limit
    ) {
        return RECOMMENDATION_SERVICE.recommend(userId, mealType, limit);
    }

    @PostMapping("/users/{userId}/select")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void selectDish(
            @PathVariable String userId,
            @RequestBody SelectRequest request
    ) {
        Dish dish = DISH_CATALOG.getById(request.getDishId());
        if (dish == null) {
            throw new IllegalArgumentException("Dish not found: " + request.getDishId());
        }
        if (request.getMealType() == null) {
            throw new IllegalArgumentException("mealType is required");
        }
        USER_SERVICE.updateOnSelection(userId, dish, request.getMealType());
        DISH_CATALOG.incrementPopularity(dish.getId());
    }

    @PostMapping("/users/{userId}/preferences")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void updatePreferences(
            @PathVariable String userId,
            @RequestBody PreferenceUpdateRequest request
    ) {
        UserProfile profile = USER_SERVICE.getOrCreate(userId);
        if (request.getRestrictions() != null) {
            profile.getRestrictions().clear();
            profile.getRestrictions().addAll(request.getRestrictions());
        }
        if (request.getCalorieGap() != null) {
            profile.setCalorieGap(Math.max(0, request.getCalorieGap()));
        }
        if (request.getProteinTargetRatio() != null) {
            profile.setProteinTargetRatio(clamp01(request.getProteinTargetRatio()));
        }
        if (request.getProteinImportance() != null) {
            profile.setProteinImportance(clamp01(request.getProteinImportance()));
        }
        if (request.getLowCalImportance() != null) {
            profile.setLowCalImportance(clamp01(request.getLowCalImportance()));
        }
        if (request.getTaste() != null) {
            TasteVector t = request.getTaste();
            profile.setSpicyPref(clamp01(t.getSpicy()));
            profile.setSweetPref(clamp01(t.getSweet()));
            profile.setSaltyPref(clamp01(t.getSalty()));
            if (t.getCookingMethod() != null) {
                // Boost preference for the specified cooking method
                profile.getCookingMethodPref().merge(t.getCookingMethod(), 0.2, Double::sum);
                normalizeMap(profile.getCookingMethodPref());
            }
        }
    }

    private static double clamp01(Double v) {
        if (v == null) return 0.0;
        return Math.max(0.0, Math.min(1.0, v));
    }

    private static void normalizeMap(Map<CookingMethod, Double> map) {
        double sum = map.values().stream().mapToDouble(d -> d).sum();
        if (sum <= 0) return;
        for (CookingMethod k : map.keySet()) {
            map.put(k, map.get(k) / sum);
        }
    }

    // --- DTOs ---

    public static class SelectRequest {
        private String dishId;
        private MealType mealType;

        public String getDishId() { return dishId; }
        public void setDishId(String dishId) { this.dishId = dishId; }
        public MealType getMealType() { return mealType; }
        public void setMealType(MealType mealType) { this.mealType = mealType; }
    }

    public static class PreferenceUpdateRequest {
        private Set<DietaryRestriction> restrictions;
        private Integer calorieGap; // 用户当前卡路里缺口
        private Double proteinTargetRatio; // 目标蛋白质比例 (0..1)
        private Double proteinImportance; // 蛋白偏好重要性 (0..1)
        private Double lowCalImportance; // 低卡偏好重要性 (0..1)
        private TasteVector taste; // 口味偏好

        public Set<DietaryRestriction> getRestrictions() { return restrictions; }
        public void setRestrictions(Set<DietaryRestriction> restrictions) { this.restrictions = restrictions; }
        public Integer getCalorieGap() { return calorieGap; }
        public void setCalorieGap(Integer calorieGap) { this.calorieGap = calorieGap; }
        public Double getProteinTargetRatio() { return proteinTargetRatio; }
        public void setProteinTargetRatio(Double proteinTargetRatio) { this.proteinTargetRatio = proteinTargetRatio; }
        public Double getProteinImportance() { return proteinImportance; }
        public void setProteinImportance(Double proteinImportance) { this.proteinImportance = proteinImportance; }
        public Double getLowCalImportance() { return lowCalImportance; }
        public void setLowCalImportance(Double lowCalImportance) { this.lowCalImportance = lowCalImportance; }
        public TasteVector getTaste() { return taste; }
        public void setTaste(TasteVector taste) { this.taste = taste; }
    }

    // --- Recommendation Service ---

    public static class RecommendationService {
        private static final int PERSONALIZATION_THRESHOLD = 10; // 10个偏好菜品后启用
        private final DishCatalog dishCatalog;
        private final UserProfileService userService;

        public RecommendationService(DishCatalog dishCatalog, UserProfileService userService) {
            this.dishCatalog = dishCatalog;
            this.userService = userService;
        }

        public List<DishScore> recommend(String userId, MealType mealType, int limit) {
            UserProfile user = userService.getOrCreate(userId);
            List<Dish> candidates = dishCatalog.getAll();

            // 过滤不满足饮食限制的菜品
            candidates = candidates.stream().filter(d -> satisfiesRestrictions(d, user.getRestrictions())).collect(Collectors.toList());

            if (user.getSelectionCount() < PERSONALIZATION_THRESHOLD) {
                // 新用户：热门推荐
                return candidates.stream()
                        .sorted(Comparator.comparingLong((Dish d) -> dishCatalog.getPopularity(d.getId())).reversed())
                        .limit(limit)
                        .map(d -> new DishScore(d, 0.0))
                        .collect(Collectors.toList());
            }

            // 个性化打分
            ArrayList<DishScore> scored = new ArrayList<>();
            for (Dish d : candidates) {
                double score = scoreDish(user, d, mealType);
                scored.add(new DishScore(d, score));
            }
            scored.sort(Comparator.comparingDouble(DishScore::getScore).reversed());
            if (scored.size() > limit) scored.subList(limit, scored.size()).clear();
            return scored;
        }

        private boolean satisfiesRestrictions(Dish d, Set<DietaryRestriction> restrictions) {
            if (restrictions == null || restrictions.isEmpty()) return true;
            for (DietaryRestriction r : restrictions) {
                if (!d.getTags().contains(r)) return false; // 必须满足全部限制
            }
            return true;
        }

        private double scoreDish(UserProfile user, Dish dish, MealType mealType) {
            // 跨餐偏好共享：用全局偏好 + 其他餐最近选择的平均值 (30%)
            TasteVector effectiveTaste = blendedTaste(user, mealType);

            // 口味相似度（0..1）
            double tasteSim = 1.0 - (
                    (Math.abs(effectiveTaste.getSpicy() - dish.getSpicy()) +
                     Math.abs(effectiveTaste.getSweet() - dish.getSweet()) +
                     Math.abs(effectiveTaste.getSalty() - dish.getSalty())) / 3.0
            );

            // 烹饪方式偏好
            double methodWeight = user.getCookingMethodPref().getOrDefault(dish.getCookingMethod(), 0.0);

            // 蛋白质匹配（越接近目标越好）
            double proteinMatch = 1.0 - Math.min(1.0, Math.abs(user.getProteinTargetRatio() - dish.getProteinRatio()));

            // 卡路里匹配：卡路里越不超过gap越好，超出gap惩罚
            double gap = Math.max(100.0, user.getCalorieGap());
            double caloriePenalty = Math.max(0.0, (dish.getCalories() - gap) / gap); // 超出部分占gap比例
            double calorieScore = 1.0 - Math.min(1.0, caloriePenalty);

            // 组合权重
            double tasteWeight = 0.4;
            double methodPrefWeight = 0.1;
            double proteinWeight = 0.25 * user.getProteinImportance();
            double calorieWeight = 0.25 * user.getLowCalImportance();

            double score = tasteWeight * tasteSim
                    + methodPrefWeight * methodWeight
                    + proteinWeight * proteinMatch
                    + calorieWeight * calorieScore;

            // 轻微加成：菜品是否匹配该餐次
            if (dish.getMealTypes().contains(mealType)) score += 0.05;

            return score;
        }

        private TasteVector blendedTaste(UserProfile user, MealType currentMeal) {
            TasteVector base = new TasteVector(user.getSpicyPref(), user.getSweetPref(), user.getSaltyPref(), null);
            // 其他餐最近taste的平均
            double oSpicy = 0, oSweet = 0, oSalty = 0; int c = 0;
            for (Map.Entry<MealType, TasteVector> e : user.getRecentTasteByMeal().entrySet()) {
                if (e.getKey() != currentMeal && e.getValue() != null) {
                    oSpicy += e.getValue().getSpicy();
                    oSweet += e.getValue().getSweet();
                    oSalty += e.getValue().getSalty();
                    c++;
                }
            }
            if (c > 0) {
                oSpicy /= c; oSweet /= c; oSalty /= c;
                double alpha = 0.3; // 30% 共享权重
                base.setSpicy(base.getSpicy() * (1 - alpha) + oSpicy * alpha);
                base.setSweet(base.getSweet() * (1 - alpha) + oSweet * alpha);
                base.setSalty(base.getSalty() * (1 - alpha) + oSalty * alpha);
            }
            return base;
        }
    }

    public static class DishScore {
        private final Dish dish;
        private final double score;
        public DishScore(Dish dish, double score) { this.dish = dish; this.score = score; }
        public Dish getDish() { return dish; }
        public double getScore() { return score; }
    }

    // --- User Service ---

    public static class UserProfileService {
        private static final double ALPHA = 0.2; // 学习率
        private final Map<String, UserProfile> users = new ConcurrentHashMap<>();

        public UserProfile getOrCreate(String userId) {
            return users.computeIfAbsent(userId, id -> UserProfile.createDefault(id));
        }

        public void updateOnSelection(String userId, Dish dish, MealType mealType) {
            UserProfile u = getOrCreate(userId);

            // 口味：朝已选菜品口味移动（EMA）
            u.setSpicyPref(ema(u.getSpicyPref(), dish.getSpicy(), ALPHA));
            u.setSweetPref(ema(u.getSweetPref(), dish.getSweet(), ALPHA));
            u.setSaltyPref(ema(u.getSaltyPref(), dish.getSalty(), ALPHA));

            // 烹饪方式偏好累积
            u.getCookingMethodPref().merge(dish.getCookingMethod(), 0.3, Double::sum); // 加强当前方法
            normalizeCookingPref(u.getCookingMethodPref());

            // 营养维度：
            // 若蛋白比例高于目标，略提升蛋白重要性；若低于目标，略提升低卡重要性（假设用户在控制热量）
            if (dish.getProteinRatio() >= u.getProteinTargetRatio()) {
                u.setProteinImportance(clamp01(u.getProteinImportance() + 0.05));
            } else {
                u.setLowCalImportance(clamp01(u.getLowCalImportance() + 0.03));
            }

            // 最近餐次口味记录（用于跨餐共享）
            u.getRecentTasteByMeal().put(mealType, new TasteVector(dish.getSpicy(), dish.getSweet(), dish.getSalty(), dish.getCookingMethod()));

            // 次数累计
            u.setSelectionCount(u.getSelectionCount() + 1);
        }

        private static double ema(double prev, double target, double alpha) {
            return prev + alpha * (target - prev);
        }

        private static void normalizeCookingPref(Map<CookingMethod, Double> map) {
            double sum = map.values().stream().mapToDouble(d -> d).sum();
            if (sum <= 0.0) {
                double uniform = 1.0 / map.size();
                for (CookingMethod cm : map.keySet()) map.put(cm, uniform);
                return;
            }
            for (CookingMethod cm : map.keySet()) map.put(cm, map.get(cm) / sum);
        }
    }

    // --- Dish Catalog (in-memory with seed data) ---

    public static class DishCatalog {
        private final Map<String, Dish> byId = new LinkedHashMap<>();
        private final Map<String, Long> popularity = new ConcurrentHashMap<>();
        private final AtomicLong idGen = new AtomicLong(1);

        public DishCatalog() {
            seed();
        }

        public List<Dish> getAll() { return new ArrayList<>(byId.values()); }
        public Dish getById(String id) { return byId.get(id); }
        public long getPopularity(String id) { return popularity.getOrDefault(id, 0L); }
        public void incrementPopularity(String id) { popularity.merge(id, 1L, Long::sum); }

        private String nextId() { return String.valueOf(idGen.getAndIncrement()); }

        private void add(Dish d, long initialPop) {
            byId.put(d.getId(), d);
            if (initialPop > 0) popularity.put(d.getId(), initialPop);
        }

        private void seed() {
            // Breakfast
            add(new Dish(nextId(), "燕麦酸奶杯", EnumSet.of(MealType.BREAKFAST), 300, 0.30,
                    0.0, 0.2, 0.1, CookingMethod.RAW, EnumSet.of(DietaryRestriction.LOW_FAT, DietaryRestriction.HIGH_PROTEIN, DietaryRestriction.VEGETARIAN)), 60);
            add(new Dish(nextId(), "蒸蛋", EnumSet.of(MealType.BREAKFAST), 180, 0.22,
                    0.0, 0.1, 0.2, CookingMethod.STEAMED, EnumSet.of(DietaryRestriction.LOW_FAT, DietaryRestriction.HIGH_PROTEIN)), 45);

            // Lunch
            add(new Dish(nextId(), "清炒西兰花鸡胸", EnumSet.of(MealType.LUNCH, MealType.DINNER), 420, 0.35,
                    0.1, 0.1, 0.4, CookingMethod.STIR_FRY, EnumSet.of(DietaryRestriction.LOW_FAT, DietaryRestriction.HIGH_PROTEIN)), 90);
            add(new Dish(nextId(), "红烧牛肉饭", EnumSet.of(MealType.LUNCH, MealType.DINNER), 780, 0.25,
                    0.4, 0.2, 0.5, CookingMethod.BRAISED, EnumSet.noneOf(DietaryRestriction.class)), 120);
            add(new Dish(nextId(), "宫保鸡丁", EnumSet.of(MealType.LUNCH, MealType.DINNER), 650, 0.28,
                    0.6, 0.2, 0.5, CookingMethod.STIR_FRY, EnumSet.noneOf(DietaryRestriction.class)), 110);
            add(new Dish(nextId(), "罗勒意面", EnumSet.of(MealType.LUNCH, MealType.DINNER), 620, 0.18,
                    0.1, 0.2, 0.6, CookingMethod.BOILED, EnumSet.of(DietaryRestriction.VEGETARIAN)), 85);

            // Dinner
            add(new Dish(nextId(), "清蒸鲈鱼", EnumSet.of(MealType.DINNER), 480, 0.32,
                    0.1, 0.2, 0.3, CookingMethod.STEAMED, EnumSet.of(DietaryRestriction.LOW_FAT, DietaryRestriction.HIGH_PROTEIN)), 70);
            add(new Dish(nextId(), "烤三文鱼", EnumSet.of(MealType.DINNER), 550, 0.34,
                    0.1, 0.2, 0.3, CookingMethod.GRILLED, EnumSet.of(DietaryRestriction.HIGH_PROTEIN)), 95);
            add(new Dish(nextId(), "麻婆豆腐", EnumSet.of(MealType.LUNCH, MealType.DINNER), 520, 0.20,
                    0.7, 0.2, 0.5, CookingMethod.STIR_FRY, EnumSet.of(DietaryRestriction.VEGETARIAN)), 100);
            add(new Dish(nextId(), "鱼香肉丝", EnumSet.of(MealType.LUNCH, MealType.DINNER), 680, 0.26,
                    0.5, 0.2, 0.6, CookingMethod.STIR_FRY, EnumSet.noneOf(DietaryRestriction.class)), 105);

            // Snack
            add(new Dish(nextId(), "水果沙拉", EnumSet.of(MealType.SNACK, MealType.BREAKFAST), 220, 0.06,
                    0.0, 0.5, 0.1, CookingMethod.RAW, EnumSet.of(DietaryRestriction.LOW_FAT, DietaryRestriction.VEGETARIAN)), 65);
            add(new Dish(nextId(), "烤地瓜", EnumSet.of(MealType.SNACK, MealType.BREAKFAST), 260, 0.07,
                    0.0, 0.3, 0.2, CookingMethod.BAKED, EnumSet.of(DietaryRestriction.VEGETARIAN, DietaryRestriction.LOW_FAT)), 55);
        }
    }

    // --- Models ---

    public enum MealType { BREAKFAST, LUNCH, DINNER, SNACK }

    public enum CookingMethod { FRIED, BOILED, STEAMED, GRILLED, STIR_FRY, RAW, BAKED, BRAISED }

    public enum DietaryRestriction { VEGETARIAN, LOW_FAT, HIGH_PROTEIN }

    public static class TasteVector {
        private double spicy; // 0..1
        private double sweet; // 0..1
        private double salty; // 0..1
        private CookingMethod cookingMethod; // 可选

        public TasteVector() {}
        public TasteVector(double spicy, double sweet, double salty, CookingMethod cookingMethod) {
            this.spicy = spicy; this.sweet = sweet; this.salty = salty; this.cookingMethod = cookingMethod;
        }
        public double getSpicy() { return spicy; }
        public void setSpicy(double spicy) { this.spicy = spicy; }
        public double getSweet() { return sweet; }
        public void setSweet(double sweet) { this.sweet = sweet; }
        public double getSalty() { return salty; }
        public void setSalty(double salty) { this.salty = salty; }
        public CookingMethod getCookingMethod() { return cookingMethod; }
        public void setCookingMethod(CookingMethod cookingMethod) { this.cookingMethod = cookingMethod; }
    }

    public static class Dish {
        private String id;
        private String name;
        private Set<MealType> mealTypes;
        private int calories; // 单份卡路里
        private double proteinRatio; // 蛋白质比例 (0..1)
        private double spicy; // 0..1
        private double sweet; // 0..1
        private double salty; // 0..1
        private CookingMethod cookingMethod;
        private Set<DietaryRestriction> tags;

        public Dish() {}
        public Dish(String id, String name, Set<MealType> mealTypes, int calories, double proteinRatio,
                    double spicy, double sweet, double salty, CookingMethod cookingMethod, Set<DietaryRestriction> tags) {
            this.id = id; this.name = name; this.mealTypes = mealTypes; this.calories = calories; this.proteinRatio = proteinRatio;
            this.spicy = spicy; this.sweet = sweet; this.salty = salty; this.cookingMethod = cookingMethod; this.tags = tags;
        }

        public String getId() { return id; }
        public String getName() { return name; }
        public Set<MealType> getMealTypes() { return mealTypes; }
        public int getCalories() { return calories; }
        public double getProteinRatio() { return proteinRatio; }
        public double getSpicy() { return spicy; }
        public double getSweet() { return sweet; }
        public double getSalty() { return salty; }
        public CookingMethod getCookingMethod() { return cookingMethod; }
        public Set<DietaryRestriction> getTags() { return tags; }

        public void setId(String id) { this.id = id; }
        public void setName(String name) { this.name = name; }
        public void setMealTypes(Set<MealType> mealTypes) { this.mealTypes = mealTypes; }
        public void setCalories(int calories) { this.calories = calories; }
        public void setProteinRatio(double proteinRatio) { this.proteinRatio = proteinRatio; }
        public void setSpicy(double spicy) { this.spicy = spicy; }
        public void setSweet(double sweet) { this.sweet = sweet; }
        public void setSalty(double salty) { this.salty = salty; }
        public void setCookingMethod(CookingMethod cookingMethod) { this.cookingMethod = cookingMethod; }
        public void setTags(Set<DietaryRestriction> tags) { this.tags = tags; }
    }

    public static class UserProfile {
        private String userId;
        // 口味偏好（0..1）
        private double spicyPref = 0.3;
        private double sweetPref = 0.3;
        private double saltyPref = 0.3;
        // 烹饪方式偏好分布
        private Map<CookingMethod, Double> cookingMethodPref = new EnumMap<>(CookingMethod.class);
        // 营养偏好
        private int calorieGap = 500; // 卡路里缺口，默认500
        private double proteinTargetRatio = 0.25; // 目标蛋白比例
        private double proteinImportance = 0.5; // ���白重要性
        private double lowCalImportance = 0.5; // 低卡重要性
        // 饮食限制
        private Set<DietaryRestriction> restrictions = EnumSet.noneOf(DietaryRestriction.class);
        // 历史计数
        private int selectionCount = 0;
        // 各餐最近taste记录
        private Map<MealType, TasteVector> recentTasteByMeal = new EnumMap<>(MealType.class);

        public static UserProfile createDefault(String userId) {
            UserProfile u = new UserProfile();
            u.userId = userId;
            // 初始化烹饪方式均匀分布
            double uniform = 1.0 / CookingMethod.values().length;
            for (CookingMethod cm : CookingMethod.values()) {
                u.cookingMethodPref.put(cm, uniform);
            }
            return u;
        }

        public String getUserId() { return userId; }
        public double getSpicyPref() { return spicyPref; }
        public void setSpicyPref(double spicyPref) { this.spicyPref = clamp01(spicyPref); }
        public double getSweetPref() { return sweetPref; }
        public void setSweetPref(double sweetPref) { this.sweetPref = clamp01(sweetPref); }
        public double getSaltyPref() { return saltyPref; }
        public void setSaltyPref(double saltyPref) { this.saltyPref = clamp01(saltyPref); }
        public Map<CookingMethod, Double> getCookingMethodPref() { return cookingMethodPref; }
        public int getCalorieGap() { return calorieGap; }
        public void setCalorieGap(int calorieGap) { this.calorieGap = Math.max(0, calorieGap); }
        public double getProteinTargetRatio() { return proteinTargetRatio; }
        public void setProteinTargetRatio(double proteinTargetRatio) { this.proteinTargetRatio = clamp01(proteinTargetRatio); }
        public double getProteinImportance() { return proteinImportance; }
        public void setProteinImportance(double proteinImportance) { this.proteinImportance = clamp01(proteinImportance); }
        public double getLowCalImportance() { return lowCalImportance; }
        public void setLowCalImportance(double lowCalImportance) { this.lowCalImportance = clamp01(lowCalImportance); }
        public Set<DietaryRestriction> getRestrictions() { return restrictions; }
        public void setRestrictions(Set<DietaryRestriction> restrictions) { this.restrictions = restrictions; }
        public int getSelectionCount() { return selectionCount; }
        public void setSelectionCount(int selectionCount) { this.selectionCount = selectionCount; }
        public Map<MealType, TasteVector> getRecentTasteByMeal() { return recentTasteByMeal; }
    }
}
